Ignore ipb_anon_only and ipb_create_account for username blocks.
[lhc/web/wiklou.git] / includes / Block.php
1 <?php
2 /**
3 * Blocks and bans object
4 * @package MediaWiki
5 */
6
7 /**
8 * The block class
9 * All the functions in this class assume the object is either explicitly
10 * loaded or filled. It is not load-on-demand. There are no accessors.
11 *
12 * Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
13 *
14 * @todo This could be used everywhere, but it isn't.
15 * @package MediaWiki
16 */
17 class Block
18 {
19 /* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
20 $mRangeStart, $mRangeEnd, $mAnonOnly;
21 /* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName;
22
23 const EB_KEEP_EXPIRED = 1;
24 const EB_FOR_UPDATE = 2;
25 const EB_RANGE_ONLY = 4;
26
27 function Block( $address = '', $user = 0, $by = 0, $reason = '',
28 $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0 )
29 {
30 $this->mId = 0;
31 $this->mAddress = $address;
32 $this->mUser = $user;
33 $this->mBy = $by;
34 $this->mReason = $reason;
35 $this->mTimestamp = wfTimestamp(TS_MW,$timestamp);
36 $this->mAuto = $auto;
37 $this->mAnonOnly = $anonOnly;
38 $this->mCreateAccount = $createAccount;
39 $this->mExpiry = self::decodeExpiry( $expiry );
40
41 $this->mForUpdate = false;
42 $this->mFromMaster = false;
43 $this->mByName = false;
44 $this->initialiseRange();
45 }
46
47 static function newFromDB( $address, $user = 0, $killExpired = true )
48 {
49 $block = new Block();
50 $block->load( $address, $user, $killExpired );
51 if ( $block->isValid() ) {
52 return $block;
53 } else {
54 return null;
55 }
56 }
57
58 static function newFromID( $id )
59 {
60 $dbr =& wfGetDB( DB_SLAVE );
61 $res = $dbr->resultObject( $dbr->select( 'ipblocks', '*',
62 array( 'ipb_id' => $id ), __METHOD__ ) );
63 $block = new Block;
64 if ( $block->loadFromResult( $res ) ) {
65 return $block;
66 } else {
67 return null;
68 }
69 }
70
71 function clear()
72 {
73 $this->mAddress = $this->mReason = $this->mTimestamp = '';
74 $this->mId = $this->mAnonOnly = $this->mCreateAccount =
75 $this->mAuto = $this->mUser = $this->mBy = 0;
76 $this->mByName = false;
77 }
78
79 /**
80 * Get the DB object and set the reference parameter to the query options
81 */
82 function &getDBOptions( &$options )
83 {
84 global $wgAntiLockFlags;
85 if ( $this->mForUpdate || $this->mFromMaster ) {
86 $db =& wfGetDB( DB_MASTER );
87 if ( !$this->mForUpdate || ($wgAntiLockFlags & ALF_NO_BLOCK_LOCK) ) {
88 $options = array();
89 } else {
90 $options = array( 'FOR UPDATE' );
91 }
92 } else {
93 $db =& wfGetDB( DB_SLAVE );
94 $options = array();
95 }
96 return $db;
97 }
98
99 /**
100 * Get a ban from the DB, with either the given address or the given username
101 *
102 * @param string $address The IP address of the user, or blank to skip IP blocks
103 * @param integer $user The user ID, or zero for anonymous users
104 * @param bool $killExpired Whether to delete expired rows while loading
105 *
106 */
107 function load( $address = '', $user = 0, $killExpired = true )
108 {
109 wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
110
111 $options = array();
112 $db =& $this->getDBOptions( $options );
113
114 $ret = false;
115 $killed = false;
116
117 if ( 0 == $user && $address == '' ) {
118 # Invalid user specification, not blocked
119 $this->clear();
120 return false;
121 }
122
123 # Try user block
124 if ( $user ) {
125 $res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ),
126 __METHOD__, $options ) );
127 if ( $this->loadFromResult( $res, $killExpired ) ) {
128 return true;
129 }
130 }
131
132 # Try IP block
133 if ( $address ) {
134 $conds = array( 'ipb_address' => $address );
135 if ( $user ) {
136 $conds['ipb_anon_only'] = 0;
137 }
138 $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
139 if ( $this->loadFromResult( $res, $killExpired ) ) {
140 return true;
141 }
142 }
143
144 # Try range block
145 if ( $this->loadRange( $address, $killExpired, $user == 0 ) ) {
146 return true;
147 }
148
149 # Give up
150 $this->clear();
151 return false;
152 }
153
154 /**
155 * Fill in member variables from a result wrapper
156 */
157 function loadFromResult( ResultWrapper $res, $killExpired = true ) {
158 $ret = false;
159 if ( 0 != $res->numRows() ) {
160 # Get first block
161 $row = $res->fetchObject();
162 $this->initFromRow( $row );
163
164 if ( $killExpired ) {
165 # If requested, delete expired rows
166 do {
167 $killed = $this->deleteIfExpired();
168 if ( $killed ) {
169 $row = $res->fetchObject();
170 if ( $row ) {
171 $this->initFromRow( $row );
172 }
173 }
174 } while ( $killed && $row );
175
176 # If there were any left after the killing finished, return true
177 if ( $row ) {
178 $ret = true;
179 }
180 } else {
181 $ret = true;
182 }
183 }
184 $res->free();
185 return $ret;
186 }
187
188 /**
189 * Search the database for any range blocks matching the given address, and
190 * load the row if one is found.
191 */
192 function loadRange( $address, $killExpired = true, $isAnon = true )
193 {
194 $iaddr = wfIP2Hex( $address );
195 if ( $iaddr === false ) {
196 # Invalid address
197 return false;
198 }
199
200 # Only scan ranges which start in this /16, this improves search speed
201 # Blocks should not cross a /16 boundary.
202 $range = substr( $iaddr, 0, 4 );
203
204 $options = array();
205 $db =& $this->getDBOptions( $options );
206 $conds = array(
207 "ipb_range_start LIKE '$range%'",
208 "ipb_range_start <= '$iaddr'",
209 "ipb_range_end >= '$iaddr'"
210 );
211 if ( !$isAnon ) {
212 $conds['ipb_anon_only'] = 0;
213 }
214
215 $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
216 $success = $this->loadFromResult( $res, $killExpired );
217 return $success;
218 }
219
220 /**
221 * Determine if a given integer IPv4 address is in a given CIDR network
222 */
223 function isAddressInRange( $addr, $range ) {
224 list( $network, $bits ) = wfParseCIDR( $range );
225 if ( $network !== false && $addr >> ( 32 - $bits ) == $network >> ( 32 - $bits ) ) {
226 return true;
227 } else {
228 return false;
229 }
230 }
231
232 function initFromRow( $row )
233 {
234 $this->mAddress = $row->ipb_address;
235 $this->mReason = $row->ipb_reason;
236 $this->mTimestamp = wfTimestamp(TS_MW,$row->ipb_timestamp);
237 $this->mUser = $row->ipb_user;
238 $this->mBy = $row->ipb_by;
239 $this->mAuto = $row->ipb_auto;
240 $this->mAnonOnly = $row->ipb_anon_only;
241 $this->mCreateAccount = $row->ipb_create_account;
242 $this->mId = $row->ipb_id;
243 $this->mExpiry = self::decodeExpiry( $row->ipb_expiry );
244 if ( isset( $row->user_name ) ) {
245 $this->mByName = $row->user_name;
246 } else {
247 $this->mByName = false;
248 }
249 $this->mRangeStart = $row->ipb_range_start;
250 $this->mRangeEnd = $row->ipb_range_end;
251 }
252
253 function initialiseRange()
254 {
255 $this->mRangeStart = '';
256 $this->mRangeEnd = '';
257 if ( $this->mUser == 0 ) {
258 list( $network, $bits ) = wfParseCIDR( $this->mAddress );
259 if ( $network !== false ) {
260 $this->mRangeStart = sprintf( '%08X', $network );
261 $this->mRangeEnd = sprintf( '%08X', $network + (1 << (32 - $bits)) - 1 );
262 }
263 }
264 }
265
266 /**
267 * Callback with a Block object for every block
268 * @return integer number of blocks;
269 */
270 /*static*/ function enumBlocks( $callback, $tag, $flags = 0 )
271 {
272 global $wgAntiLockFlags;
273
274 $block = new Block();
275 if ( $flags & Block::EB_FOR_UPDATE ) {
276 $db =& wfGetDB( DB_MASTER );
277 if ( $wgAntiLockFlags & ALF_NO_BLOCK_LOCK ) {
278 $options = '';
279 } else {
280 $options = 'FOR UPDATE';
281 }
282 $block->forUpdate( true );
283 } else {
284 $db =& wfGetDB( DB_SLAVE );
285 $options = '';
286 }
287 if ( $flags & Block::EB_RANGE_ONLY ) {
288 $cond = " AND ipb_range_start <> ''";
289 } else {
290 $cond = '';
291 }
292
293 $now = wfTimestampNow();
294
295 extract( $db->tableNames( 'ipblocks', 'user' ) );
296
297 $sql = "SELECT $ipblocks.*,user_name FROM $ipblocks,$user " .
298 "WHERE user_id=ipb_by $cond ORDER BY ipb_timestamp DESC $options";
299 $res = $db->query( $sql, 'Block::enumBlocks' );
300 $num_rows = $db->numRows( $res );
301
302 while ( $row = $db->fetchObject( $res ) ) {
303 $block->initFromRow( $row );
304 if ( ( $flags & Block::EB_RANGE_ONLY ) && $block->mRangeStart == '' ) {
305 continue;
306 }
307
308 if ( !( $flags & Block::EB_KEEP_EXPIRED ) ) {
309 if ( $block->mExpiry && $now > $block->mExpiry ) {
310 $block->delete();
311 } else {
312 call_user_func( $callback, $block, $tag );
313 }
314 } else {
315 call_user_func( $callback, $block, $tag );
316 }
317 }
318 wfFreeResult( $res );
319 return $num_rows;
320 }
321
322 function delete()
323 {
324 if (wfReadOnly()) {
325 return false;
326 }
327 if ( !$this->mId ) {
328 throw new MWException( "Block::delete() now requires that the mId member be filled\n" );
329 }
330
331 $dbw =& wfGetDB( DB_MASTER );
332 $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ );
333 return $dbw->affectedRows() > 0;
334 }
335
336 function insert()
337 {
338 wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
339 $dbw =& wfGetDB( DB_MASTER );
340 $dbw->begin();
341
342 # Unset ipb_anon_only and ipb_create_account for user blocks, makes no sense
343 if ( $this->mUser ) {
344 $this->mAnonOnly = 0;
345 $this->mCreateAccount = 0;
346 }
347
348 # Don't collide with expired blocks
349 Block::purgeExpired();
350
351 $ipb_id = $dbw->nextSequenceValue('ipblocks_ipb_id_val');
352 $dbw->insert( 'ipblocks',
353 array(
354 'ipb_id' => $ipb_id,
355 'ipb_address' => $this->mAddress,
356 'ipb_user' => $this->mUser,
357 'ipb_by' => $this->mBy,
358 'ipb_reason' => $this->mReason,
359 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
360 'ipb_auto' => $this->mAuto,
361 'ipb_anon_only' => $this->mAnonOnly,
362 'ipb_create_account' => $this->mCreateAccount,
363 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
364 'ipb_range_start' => $this->mRangeStart,
365 'ipb_range_end' => $this->mRangeEnd,
366 ), 'Block::insert', array( 'IGNORE' )
367 );
368 $affected = $dbw->affectedRows();
369 $dbw->commit();
370 return $affected;
371 }
372
373 function deleteIfExpired()
374 {
375 $fname = 'Block::deleteIfExpired';
376 wfProfileIn( $fname );
377 if ( $this->isExpired() ) {
378 wfDebug( "Block::deleteIfExpired() -- deleting\n" );
379 $this->delete();
380 $retVal = true;
381 } else {
382 wfDebug( "Block::deleteIfExpired() -- not expired\n" );
383 $retVal = false;
384 }
385 wfProfileOut( $fname );
386 return $retVal;
387 }
388
389 function isExpired()
390 {
391 wfDebug( "Block::isExpired() checking current " . wfTimestampNow() . " vs $this->mExpiry\n" );
392 if ( !$this->mExpiry ) {
393 return false;
394 } else {
395 return wfTimestampNow() > $this->mExpiry;
396 }
397 }
398
399 function isValid()
400 {
401 return $this->mAddress != '';
402 }
403
404 function updateTimestamp()
405 {
406 if ( $this->mAuto ) {
407 $this->mTimestamp = wfTimestamp();
408 $this->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
409
410 $dbw =& wfGetDB( DB_MASTER );
411 $dbw->update( 'ipblocks',
412 array( /* SET */
413 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
414 'ipb_expiry' => $dbw->timestamp($this->mExpiry),
415 ), array( /* WHERE */
416 'ipb_address' => $this->mAddress
417 ), 'Block::updateTimestamp'
418 );
419 }
420 }
421
422 /*
423 function getIntegerAddr()
424 {
425 return $this->mIntegerAddr;
426 }
427
428 function getNetworkBits()
429 {
430 return $this->mNetworkBits;
431 }*/
432
433 function getByName()
434 {
435 if ( $this->mByName === false ) {
436 $this->mByName = User::whoIs( $this->mBy );
437 }
438 return $this->mByName;
439 }
440
441 function forUpdate( $x = NULL ) {
442 return wfSetVar( $this->mForUpdate, $x );
443 }
444
445 function fromMaster( $x = NULL ) {
446 return wfSetVar( $this->mFromMaster, $x );
447 }
448
449 function getRedactedName() {
450 if ( $this->mAuto ) {
451 return '#' . $this->mId;
452 } else {
453 return $this->mAddress;
454 }
455 }
456
457 /**
458 * Encode expiry for DB
459 */
460 static function encodeExpiry( $expiry, $db ) {
461 if ( $expiry == '' || $expiry == Block::infinity() ) {
462 return Block::infinity();
463 } else {
464 return $db->timestamp( $expiry );
465 }
466 }
467
468 /**
469 * Decode expiry which has come from the DB
470 */
471 static function decodeExpiry( $expiry ) {
472 if ( $expiry == '' || $expiry == Block::infinity() ) {
473 return Block::infinity();
474 } else {
475 return wfTimestamp( TS_MW, $expiry );
476 }
477 }
478
479 static function getAutoblockExpiry( $timestamp )
480 {
481 global $wgAutoblockExpiry;
482 return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
483 }
484
485 static function normaliseRange( $range )
486 {
487 $parts = explode( '/', $range );
488 if ( count( $parts ) == 2 ) {
489 $shift = 32 - $parts[1];
490 $ipint = wfIP2Unsigned( $parts[0] );
491 $ipint = $ipint >> $shift << $shift;
492 $newip = long2ip( $ipint );
493 $range = "$newip/{$parts[1]}";
494 }
495 return $range;
496 }
497
498 /**
499 * Purge expired blocks from the ipblocks table
500 */
501 static function purgeExpired() {
502 $dbw =& wfGetDB( DB_MASTER );
503 $dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
504 }
505
506 static function infinity() {
507 # This is a special keyword for timestamps in PostgreSQL, and
508 # works with CHAR(14) as well because "i" sorts after all numbers.
509 return 'infinity';
510
511 /*
512 static $infinity;
513 if ( !isset( $infinity ) ) {
514 $dbr =& wfGetDB( DB_SLAVE );
515 $infinity = $dbr->bigTimestamp();
516 }
517 return $infinity;
518 */
519 }
520
521 }
522 ?>